﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ESpace;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using ElementClasses;
using System.Reflection;

/*
 ДОБАВЛЕНИЕ НОВОГО ТИПА ЭЛЕМЕНТОВ
 * создать новые классы юнита и самого типа
 * в методе Gl.LoadUnins прописать добавление нового типа элементов
 */

namespace Organizer
{
    public class Gl
    {
        public enum SinhronizeResult { NEW_VERTION, VERTION_OK, WRITE_OK, ERROR }

        public static readonly string LocHideDirName = "LocDir~";
        public static readonly string NetHideDirName = "NetDir~";

        public static long ServVertion = 0;
        public static string fPathDB;
        public static string fPathSett;
        public static string dPathHistory;
        public static string dPathDebug;
        public static string dPathExternalExe;
        public static string dPathExternalNetUnits;
        public static string dPathExternalNetLocalData;
        public static string dPathExternalNetHistory;
        public static Settings sett;

        public static API_Funtions ExtFunc;

        public enum ConnectState { OnLine, Sync, OffLine }
        static ConnectState connState_ = ConnectState.OffLine;
        public static ConnectState ConnState { get { return connState_; } set { connState_ = value; if (connState_ == ConnectState.OffLine) IsActualFlag = false; } }

        /// <summary> Локальная версия равна версии на сервере. Возводится, когда версии совпали. Сбразывается, если возведен статус соединения offline </summary>
        public static bool IsActualFlag = false;

        public static Dictionary<string, IElementUnit> unL = new Dictionary<string, IElementUnit>();
        public static List<ExtElementUnit> exl = new List<ExtElementUnit>();

        public static List<ExternalExe> exExeL = new List<ExternalExe>();

        // пока вручную
        public static void LoadUnins(ImageList imL)
        {
            #region *** модули элементов ***
            IElementUnit unt = new NoteUnit(ExtFunc);
            unL.Add(unt.Ext, unt);

            unt = new DirUnit(ExtFunc);
            unL.Add(unt.Ext, unt);

            unt = new MessUnit(ExtFunc);
            unL.Add(unt.Ext, unt);

            unt = new StickerUnit(ExtFunc);
            unL.Add(unt.Ext, unt);

            #endregion *** модули элементов ***

            #region *** иконки для видимых модулей ***
            imL.Images.Clear();
            imL.Images.Add(Properties.Resources.up);
            imL.Images.Add(Properties.Resources.folder_hide);
            imL.Images.Add(Properties.Resources.elm_hide);
            foreach (KeyValuePair<string, IElementUnit> u in unL)
            {
                if (!u.Value.IsVisibleUnit) continue;
                VisibleUnit vUnit = (VisibleUnit)u.Value;
                for (int i = 0; i < vUnit.IcoList.Count; i++)
                {
                    imL.Images.Add(vUnit.IcoList[i], Color.Transparent);
                    vUnit.icoIndL.Add(imL.Images.Count - 1);
                }
            }
            #endregion *** иконки для видимых модулей ***

            #region *** внелементы ***

            //ExternalElement extElm = new DayBookElement(ExtFunc);
            //exl.Add(extElm);

            #endregion *** внелементы ***
        }

        public static void LoadUninsExElm()
        {
            string[] fl = Directory.GetFiles(dPathExternalNetUnits, "*.dll");
            if (fl.Length > 0) AddDebugLogTitle("ЗАГРУЗКА ВНЕШНИХ СЕТЕВЫХ ЭЛЕМЕНТОВ");
            foreach (string fPath in fl)
            {
                AddDebugLog("\t Библиотека " + (new FileInfo(fPath)).Name, false);
                Assembly assembly = Assembly.LoadFile(fPath);
                ExternalElement elm = null;
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.BaseType == typeof(ExternalElement))
                    {
                        ConstructorInfo defaultConstructor = type.GetConstructor(new Type[0]);
                        object instance = defaultConstructor.Invoke(new object[0]);
                        elm = instance as ExternalElement;
                    }
                }

                if (elm != null)
                {
                    AddDebugLog("\t Найден Элемент " + elm.Name, false);

                    // проверка на совпадение уникального имени
                    bool bDuplicate = false;
                    foreach (ExtElementUnit exU in exl)
                    {
                        if (exU.ExElm.Name.ToUpper().Trim() == elm.Name.ToUpper().Trim()) { bDuplicate = true; break; }
                    }

                    // если не дубликат, добавляем
                    if (!bDuplicate)
                    {
                        elm.Init();
                        ExtElementUnit ExU = new ExtElementUnit(elm);
                        exl.Add(ExU);
                        AddDebugLog("\t Элемент загружен", false);
                    }
                    else
                    {
                        AddDebugLog("\t ОШИБКА! Элемент с таким уникальным именем уже загружен ранее", true);
                    }

                }
                else
                {
                    AddDebugLog("\t ОШИБКА! В библиотеке не найден требуемый класс", true);
                }
            }
        }

        public static int GetIcoInd(Element el)
        {
            if (!el.IsHider)
                return ((VisibleUnit)el.Glob).GetNumIco(el);
            else return el.Ext == "" ? 1 : 2;

            //return 0;
        }

        public enum SystemDir { Recycler, Net, Local }

        public static DIR GetSystemDir(SystemDir dir)
        {
            return (DIR)DB.Items[DB.IndexOfRecord(dir.ToString() + ".")];
        }

        /// <summary> Возвращает раздел в котором содержится элемент (локальный, облако, корзина) </summary>
        public static DIR GetParentSystemDir(Element el)
        {
            DIR ret;
            if (el.Ext == "") ret = (DIR)el;
            else ret = (DIR)el.Parent;
            while (true)
            {
                if (ret.IsSystemDir) return ret;
                ret = (DIR)ret.Parent;
            }
        }

        static int ActVerDb = 3;

        public static DIR DB;
        public static DeleteNetCollection DelList; // список более несуществующих итемов

        #region *** Save/Load ******************************************

        public static void SaveDB() { SaveDB(fPathDB); }

        static void SaveDB(string fPath)
        {
            EStream st = new EStream(fPath);
            st.OpenSave();
            st.SaveInt(ActVerDb);
            
            string giud = Guid.NewGuid().ToString();
            st.SaveStr(giud);
            st.SaveLong(ServVertion);
            DB.Save(st);
            st.SaveStr(giud);
            st.Close();
        }

        public static void LoadDB()
        {
            try
            {
                LoadDB(fPathDB);
                if (DB.Items == null) throw new Exception("некорректные данные");
            }
            catch
            {
                DateTime histDt = LoadFromHist();
                if (histDt == DateTime.MinValue)
                {
                    DB = new DIR("", null, NetStatus.Local);
                    MessageBox.Show("Не удалось загрузить сохраненные данные. База данных создана заново");
                }
                else
                {
                    MessageBox.Show("Не удалось загрузить сохраненные данные. База данных восстановлена от " + histDt.ToString("dd.MM.yyyy HH:mm:ss"));
                    SaveDB();
                }
            }

            bool bResave = false;

            // проверка наличия системных каталогов
            string[] sl = Enum.GetNames(typeof(SystemDir));
            for (int i = 0; i < sl.Length; i++)
            {
                if (DB.IndexOfRecord(sl[i] + ".") == -1)
                {
                    DIR dir = new DIR(sl[i], DB, i == 1 ? NetStatus.NotActual : NetStatus.Local);
                    dir.IsSystemDir = true;
                    DB.Items.Add(dir);
                    bResave = true;
                }
            }

            // проверка наличия каталогов для скрытых модулей
            if (BuildHiderDirIfNotExist()) bResave = true;
            if (bResave) SaveDB();

            // сохранение новой истории после удачной загрузки
            SaveHistDB();
        }

        /// <summary>Попытка загрузки из истории. Возвращает дату от которой загружено, либо MinValue если загрузка не удалась</summary>
        static DateTime LoadFromHist()
        {
            List<FileInfo> fiL = GetSortHistFiles();
            foreach (FileInfo fi in fiL)
            {
                try
                {
                    LoadDB(fi.FullName);
                    Gl.SaveDB();
                    return fi.LastWriteTime;
                }
                catch { }
            }
            return DateTime.MinValue;
        }

        static void LoadDB(string fPath)
        {
            if (!File.Exists(fPath))
            {
                DB = new DIR("", null, NetStatus.Local);
            }
            else
            {
                EStream st = new EStream(fPath);
                st.OpenLoad();
                int LoadVer = st.LoadInt();
                switch (LoadVer)
                {
                    case 0: LoadDB0(st); break;
                    case 1: LoadDB1(st); break;
                    case 2: LoadDB2(st); break;
                    case 3: LoadDB3(st); break;
                    default: throw new Exception("Невозможно загрузить базу данных, так как версия не поддерживается");
                }

                st.Close();
            }
        }

        static void SaveHistDB()
        {
            if (!Directory.Exists(dPathHistory)) Directory.CreateDirectory(dPathHistory);
            Gl.SaveDB(dPathHistory + "\\LocalData_" + Comm.GetValidFileName(Gl.sett.Login) + " [" + DateTime.Now.ToString("dd.MM.yyyy HH-mm") + "].db");
            // удаление старой истории (оставляет последние 30 элементов)
            List<FileInfo> fiL = GetSortHistFiles();
            for (int i = 30; i < fiL.Count; i++)
                File.Delete(fiL[i].FullName);
        }

        static List<FileInfo> GetSortHistFiles()
        {
            if (!Directory.Exists(dPathHistory)) return new List<FileInfo>();
            DirectoryInfo dirInf = new DirectoryInfo(dPathHistory);
            List<FileInfo> fi = new List<FileInfo>(dirInf.GetFiles("*.db"));
            fi.Sort((f1, f2) => f2.LastWriteTimeUtc.CompareTo(f1.LastWriteTimeUtc));
            return fi;
        }

        static void LoadDB0(EStream st)
        {
            ServVertion = 0; st.LoadDate();
            DB = new DIR(st, null);
        }

        static void LoadDB1(EStream st)
        {
            ServVertion = st.LoadLong();
            DB = new DIR(st, null);
        }

        static void LoadDB2(EStream st)
        {
            ServVertion = st.LoadLong();
            DB = new DIR(st, null);
        }

        static void LoadDB3(EStream st)
        {
            string guid = st.LoadStr();
            LoadDB2(st);
            string guidControl = st.LoadStr();
            if (guid != guidControl) throw new Exception("данные не корректны");
        }

        public static DBItem LoadRec(EStream st, string ext, DBItem Parent)
        {
            return unL[ext].LoadElement(st, Parent);
        }

        #endregion *** Save/Load ******************************************

        /// <summary> проверка наличия каталогов для скрытых модулей </summary>
        static bool BuildHiderDirIfNotExist()
        {
            bool bResave = false;
            DIR LocDir = GetSystemDir(SystemDir.Local);
            DIR NetDir = GetSystemDir(SystemDir.Net);
            foreach (KeyValuePair<string, IElementUnit> u in unL)
            {
                if (!u.Value.IsVisibleUnit)
                {
                    string LocName = LocHideDirName + u.Value.Ext;
                    string NetName = NetHideDirName + u.Value.Ext;

                    if (LocDir.IndexOfRecord(LocName, "") == -1)
                    {
                        DIR dir = new DIR(LocName, LocDir, NetStatus.Local);
                        dir.IsHide = true;
                        LocDir.Items.Add(dir);
                        bResave = true;
                    }

                    if (NetDir.IndexOfRecord(NetName, "") == -1)
                    {
                        DIR dir = new DIR(NetName, NetDir, NetStatus.NotActual);
                        dir.IsHide = true;
                        NetDir.Items.Add(dir);
                        bResave = true;
                    }
                }
            }

            return bResave;
        }

        static public void ErrMess(string mess) { MessageBox.Show(mess, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); }

        public static string CheckName(DIR Dir, Element EditEl, string newName)
        {
            string nm = newName.ToUpper();
            if (EditEl != null && nm == EditEl.Name.ToUpper()) return "";
            if (nm == "") return "Имя не может быть пустым";
            if (nm.IndexOf("\\") != -1) return "Имя сожержит запрешенные символы";
            if (Dir.IndexOfRecord(nm) != -1) return "Такое имя уже существует";
            return "";
        }

        public static FormDebug frmDebug = null;

        public static void AddDebugLog(string Title, bool bError)
        {
            AddDebugLog(Title, "", false, bError);
        }

        public static void AddDebugLogTitle(string Title)
        {
            AddDebugLog(Title, "", true, false);
        }

        public static void AddDebugLog(string Title, string Txt, bool bError)
        {
            AddDebugLog(Title, Txt, false, bError);
        }

        static void AddDebugLog(string Title, string Txt, bool bTitle, bool bError)
        {
            if (!sett.WRITE_LOG) return;

            if (!Directory.Exists(dPathDebug)) Directory.CreateDirectory(dPathDebug);

            if (frmDebug == null) frmDebug = new FormDebug();
            if (!frmDebug.Visible && sett.DEBUG_MODE) frmDebug.Show();
            frmDebug.AddLog(Title, Txt, bTitle, bError);
        }

        /// <summary> Возвращает строку пути элемента el относительно системного каталога sysDir </summary>
        public static string GetOtnPath(SystemDir sysDir, String elFullPath)
        {
            DIR dir = GetSystemDir(sysDir);
            if (elFullPath.IndexOf(dir.FullPath) != 0) throw new Exception("Попытка узнать относительный путь у элемента, который не принадлежит указаному каталогу");
            return elFullPath.Substring(dir.FullPath.Length + 1);
        }

        #region *** Синхронизация ********************************************************

        /// <summary>Ожидающие синхронизации новые данные с сервера для внутренних элементов</summary>
        public static SyncDataInEl SyncData = null;

        public static void Sinhronize(DIR newNetDir)
        {
            /* if (ansv.res == ServerAnswer.WriteOK)
             {
                 Gl.LocalVertion = ansv.ServVertion;
                 Gl.SaveDB(); // пересохраняем базу с новыми данными о версии
                 return;
             }*/

            AddDebugLogTitle("СИНХРОНИЗАЦИЯ");

            DIR NetDir = Gl.GetSystemDir(Gl.SystemDir.Net);
            SetNotbSinh(NetDir); // помечаем все элементы перед синхронизацией
            List<string> strL = SinhronizeElements(NetDir, newNetDir); // синхронизируем одинаковые элементы и добавляем недостающие элементы с сервера
            DeleteNotSyncElements(NetDir, strL); // удаляем несинхронизированные элементы (кроме вновь добавленых)

            // проверка на наличие каталогов скрытых элементов (на сервере могло и не быть. Если сервер новый или модуль был добавлен недавно, но потребовалась синхронизация)
            BuildHiderDirIfNotExist();

        }

        /// <summary>Синхронизация внелемента </summary>
        public static void SinhronizeExElm(ExtElementUnit ElU)
        {
            AddDebugLogTitle("СИНХРОНИЗАЦИЯ ВНЕЛЕМЕНТА " + ElU.ExElm.Name);

            // если внелемент редактировался
            if (ElU.NetStat == eeNetStatus.NotActual)
            {
                // версия клиента старше
                if (ElU.ServVertion < ElU.SyncServVer)
                {
                    AddDebugLog("\t- НАЙДЕН NOT_ACTUAL внелемент" + ElU.ExElm.Name, false);
                    // смотрим идентичность. Если SourceData старого и нового идентичны, просто обновляем версию
                    if (ElU.ExElm.GetSourceData().SequenceEqual(ElU.SyncSourceData))
                    {
                        ElU.NetStat = eeNetStatus.Actual;
                        ElU.ServVertion = ElU.SyncServVer;
                        AddDebugLog("\t\t- обновлена версия так как SourceData серверного и локального внелдементов оказались одинаковыми", false);
                    }
                    // если данные отличаются
                    else
                    {
                        // пробуем дописать внелемент. 
                        TrySynchronizeResult TrySyncRes = ElU.ExElm.TrySynchronizeNotActual(ElU.SyncSourceData);
                        //Если не получается
                        if (TrySyncRes == TrySynchronizeResult.SourceDataCanBeReplaced)
                        {
                            //на его места ставим серверный внелемент
                            ElU.ServVertion = ElU.SyncServVer;
                            ElU.NetStat = eeNetStatus.Actual;
                            ElU.ExElm.ChangeSourceData(ChangeDataEvents.ReWriteFromServer, ElU.SyncSourceData);
                            AddDebugLog("\t\t- замена SourceData локального отредактированного Внелемента", false);
                        }
                        else
                        {
                            // ставим NotActual, так как после дописывания данные поменялись
                            ElU.NetStat = eeNetStatus.NotActual;
                            ElU.ServVertion = ElU.SyncServVer;
                            AddDebugLog("\t\t- дописывание новыми данными локального отредактированного Внелемента", false);
                        }
                    }
                }
                else
                {
                    // оставляем локальный внелемент вместо серверного, если версия этого внелемента >= серверной, и его данные менялись (то есть, требуют записи на сервер)
                    AddDebugLog("\t- ОСТАВЛЕН локальный отредактированный Внелемент вместо серверного " + ElU.ExElm.Name, false);
                }
            }
            // внелемент не редактировался. Можно заменять
            else
            {
                //заменяем элемент
                ElU.ServVertion = ElU.SyncServVer;
                ElU.ExElm.ChangeSourceData(ChangeDataEvents.ReWriteFromServer, ElU.SyncSourceData);
                ElU.NetStat = eeNetStatus.Actual;
                AddDebugLog("\t- ОБНОВЛЕНИЕ SourceData актуального Внелемента " + ElU.ExElm.Name, false);
            }

            ElU.SyncSourceData = null;
        }

        // установка флага перед синхронизацией
        static void SetNotbSinh(Element el)
        {
            el.bSinh = false;
            AddDebugLog("Сброс флага синхронизации для " + el.FullPath, false);

            if (el.IsDirectory)
            {
                for (int i = 0; i < el.Items.Count; i++)
                {
                    SetNotbSinh((Element)el.Items[i]);
                }
            }
        }

        /// <summary> синхронизация двух одноименных элементов + добавляются новый дочерние элементы с новой версиецй </summary>
        static List<String> SinhronizeElements(Element oldEl, Element newEl)
        {
            List<String> ret = new List<string>();

            // если это каталог для других элементов, просто синхронизируем все вложенное
            if (oldEl.Ext == "")
            {
                //помечаем элемент, как обновленный
                oldEl.bSinh = true;
                oldEl.ServVertion = newEl.ServVertion;

                // пробегаемся по всем вложеным элементам
                for (int i = 0; i < newEl.Items.Count; i++)
                {
                    // если старая запись найдена
                    int ind = oldEl.IndexOfRecord(newEl.Items[i].NameExt);
                    // если елемент на клиенте есть, синхронизируем его
                    if (ind != -1)
                    {
                        AddDebugLog("Синхронизация существующего элемента " + oldEl.Items[ind].FullPath, false);
                        SinhronizeElements((Element)oldEl.Items[ind], (Element)newEl.Items[i]);
                    }
                    // если элемента на клиенте нет, добавляем элемент с сервера
                    else
                    {
                        ((Element)newEl.Items[i]).bSinh = true;
                        newEl.Items[i].Parent = oldEl;
                        oldEl.Items.Add(newEl.Items[i]);

                        AddDebugLog("Добавление нового элемента со всеми его дочерними элементами " + newEl.Items[i].FullPath, false);
                    }
                }
            }
            else
            {
                // если элемент редактировался
                if (oldEl.NetStat == NetStatus.NotActual)
                {
                    // версия клиента старше
                    if (oldEl.ServVertion < newEl.ServVertion)
                    {
                        // смотрим идентичность. Если SourceData старого и нового идентичны, просто обновляем версию
                        if (oldEl.SourceData == newEl.SourceData)
                        {
                            oldEl.NetStat = NetStatus.Actual;
                            oldEl.ServVertion = newEl.ServVertion;
                            AddDebugLog("    - ОБНОВЛЕНА версия так как SourceData серверного и локального элементов оказались одинаковыми " + oldEl.FullPath, false);
                        }
                        // если данные отличаются
                        else
                        {
                            // пробуем дописать элемент. 
                            TrySynchronizeResult TrySyncRes = oldEl.TrySynchronize(newEl);
                            // Если не получается
                            if (TrySyncRes == TrySynchronizeResult.SourceDataCanBeReplaced)
                            {
                                //на его места ставим серверный элемент
                                oldEl.SourceData = newEl.SourceData;
                                oldEl.NetStat = NetStatus.Actual;
                                oldEl.ServVertion = newEl.ServVertion;
                                AddDebugLog("    - ЗАМЕНА SourceData локального отредактированного элемента " + oldEl.FullPath, false);
                            }
                            // если элемент дописан новыми данными без замены
                            else
                            {
                                // ставим NotActual, так как после дописывания данные поменялись
                                oldEl.NetStat = NetStatus.NotActual;
                                oldEl.ServVertion = newEl.ServVertion;
                                AddDebugLog("    - ДОПИСЫВАНИЕ новыми данными локального отредактированного элемента " + oldEl.FullPath, false);
                            }
                        }
                    }
                    else
                    {
                        // оставляем локальный элемент вместо серверного, если версия этого элемента >= серверной, и его данные менялись (то есть, требуют записи на сервер)
                        AddDebugLog("   - ОСТАВЛЕН локальный отредактированный элемент вместо серверного " + oldEl.FullPath, false);
                    }
                }
                else
                {
                    //заменяем элемент
                    oldEl.SourceData = newEl.SourceData;
                    oldEl.NetStat = NetStatus.Actual;
                    oldEl.ServVertion = newEl.ServVertion;
                    AddDebugLog("    - Обновление SourceData актуального элемента " + oldEl.FullPath, false);
                }
                //помечаем элемент, как обновленный
                oldEl.bSinh = true;
            }

            return ret;

        }

        /// <summary> Удаление всех несинхронизированных элементов. (кроме вновь добавленых) </summary>
        static void DeleteNotSyncElements(DIR dir, List<string> strL)
        {
            for (int i = 0; i < dir.Items.Count; i++)
            {
                Element elm = (Element)dir.Items[i];

                // если элемент не синхронизировался (то есть, его нет на сервере)
                // и этот элемент не вновь добавленый
                // удаляем его
                if (!elm.bSinh && !elm.IsNew)
                {
                    /* AddDebugLog("УДАЛЕНИЕ неактуального отредактированного элемента " + elm.FullPath);
                     //говорим о том, что редактируемый элемент был заменен серверной версией и локальный удален в корзину
                     strL.Add("Удален редактированный элемент '" + GetOtnPath(SystemDir.Net, elm.FullPath) + "', так как его больше нет на сервере");
                     */
                    DeleteItem(elm, false);
                    /*if (!elm.IsHider)
                    {
                        //копируем элемент в корзину
                        AddToRecycler(elm);
                    }
                    else
                    {
                        elm.Dispose();
                    }*/
                    elm.Dispose();
                    i--;
                    continue;
                }

                // если элемент - это каталог, смотрим его дочерние элементы
                if (dir.Items[i].Ext == "") DeleteNotSyncElements((DIR)dir.Items[i], strL);
            }
        }

        /// <summary>
        /// Сброс статуса записи для сетевых элементов.
        /// Если элементы удачно записаны на сервер, им обновляется версия и статус на аутуальный
        /// </summary>
        /// <param name="bWriteOk">Элементы удачно записаны на сервер</param>
        public static void ResetWriteStatus(bool bWriteOk)
        {
            DIR NetDir = Gl.GetSystemDir(Gl.SystemDir.Net);
            List<DBItem> el = Gl.GetAllItems(NetDir);
            for (int i = 0; i < el.Count; i++)
            {
                Element elm = (Element)el[i];
                if (elm.NetStat == NetStatus.Write)
                {
                    if (bWriteOk)
                    {
                        AddDebugLog("Сброс статуса записи на актуальный " + elm.FullPath, false);
                        elm.NetStat = NetStatus.Actual;
                        elm.ServVertion = Gl.ServVertion;
                    }
                    else
                    {
                        AddDebugLog("Сброс статуса записи на НЕактуальный " + elm.FullPath, false);
                        elm.NetStat = NetStatus.NotActual;
                    }
                }
            }

        }

        public static void ResetWriteStatusAllExEl()
        {
            // внелементы
            for (int i = 0; i < exl.Count; i++)
            {
                if (exl[i].NetStat == eeNetStatus.Write)
                {
                    AddDebugLog("Сброс статуса внелемента на неактуальный " + exl[i].ExElm.Name, false);
                    exl[i].NetStat = eeNetStatus.NotActual;
                }
            }
        }

        #endregion *** Синхронизация ********************************************************

        /// <summary> добавление элемента в список удаления на сервере </summary>
        public static void AddElInDeleteNetList(Element el)
        {
            // если каталог
            if (el.Ext == "")
            {
                List<DBItem> chEl = GetAllItems((DIR)el);
                for (int i = 0; i < chEl.Count; i++) DelList.Add(chEl[i].FullPath);  // добавляем в лист удаления все дочерние элементы
            }
            DelList.Add(el.FullPath);
        }

        /// <summary> Удаление элемента. Удаляет элемент из каталога (НЕ УНИЧТОЖАЕТ); добавляет элемент в список на удаление для сервера, если элемент сетевой </summary>
        public static void DeleteItem(Element elm)
        {
            DeleteItem(elm, true);
        }

        /// <summary> Удаление элемента. Удаляет элемент из каталога (НЕ УНИЧТОЖАЕТ); добавляет элемент в список на удаление для сервера, если элемент сетевой и стоит bAddInDelList </summary>
        public static void DeleteItem(Element elm, bool bAddInDelList)
        {
            DIR Dir = (DIR)elm.Parent;
            if (elm.NetStat != NetStatus.Local && bAddInDelList) AddElInDeleteNetList(elm);
            Dir.Items.Remove(elm);
        }

        public static void AddToRecycler(Element elm)
        {
            DIR RecyclerDir = GetSystemDir(SystemDir.Recycler);
            List<DBItem> el = new List<DBItem>();
            if (elm.Ext == "")
            {
                el = Gl.GetAllItems((DIR)elm);
            }
            else
            {
                el.Add(elm);
            }

            for (int i = 0; i < el.Count; i++)
            {
                if (el[i].Ext != "")
                {
                    ((Element)el[i]).NetStat = NetStatus.Local;
                    el[i].Name = RecyclerDir.GetUnicName(el[i].Name, el[i].Ext);
                    RecyclerDir.Items.Add(el[i]);
                }
            }
        }

        /// <summary> Возвращает все элементы в каталоге с известными расширениями </summary>
        public static List<DBItem> GetAllItems(DIR dir)
        {
            List<string> extToFing = new List<string>();
            foreach (KeyValuePair<string, IElementUnit> u in Gl.unL)
            {
                extToFing.Add(u.Key);
            }
            return dir.GetAllRecords(extToFing.ToArray());
        }

        public static void SaveSettings() { sett.Save(); }

        public static void LoadExternalEXE(ContextMenuStrip menu)
        {
            exExeL.Clear();

            // загрузка файлов информации
            // каждый файл должен храниться в одноименном каталоге. В этом же каталоге должен храниться exe
            string[] dl = Directory.GetDirectories(dPathExternalExe);
            foreach (string dir in dl)
            {
                string dirName = new FileInfo(dir).Name;
                exExeL.Add(new ExternalExe(dPathExternalExe + "\\" + dirName + "\\Config.cnfg", menu));
            }
        }

    }

    public enum ExElmResultStat { NewVertion, VertionOK, WriteOK }
    public class ServExtElementData
    {
        public ExElmResultStat stat;
        public ExtElementUnit OldExU;
        public long ServVer;
        public byte[] SourceData;
    }

    public class SyncDataInEl
    {
        public DIR Dir;
        public long ServVertion;
        public SyncDataInEl(DIR Dir, long ServVertion) { this.Dir = Dir; this.ServVertion = ServVertion; }
    }

    public class Settings
    {
        //public bool ShowStickers = false;
        //public int SecondBeforFirstMessage = 20; // секунд ожидания после запуска перед появлением первого сообщения
        public bool DEBUG_MODE = false;
        public bool WRITE_LOG = false;
        public bool Guests = false;

        public string Login = "";
        public string PassMd5 = "";

        public string SqlConnString { get { return "Database=" + Sql_DBName + ";Data Source=" + Sql_URL + ";User Id=" + Sql_Login + ";Password=" + Sql_Pass; } }

        public string Sql_URL = "localhost";
        public string Sql_DBName = "organizer";
        public string Sql_Login = "root";
        public string Sql_Pass = "";

        int Frm_Top = 335;
        int Frm_Left = 625;
        int Frm_Width = 669;
        int Frm_Height = 380;
        public Point Frm_Location { get { return new Point(Frm_Left, Frm_Top); } set { Frm_Left = value.X; Frm_Top = value.Y; } }
        public Size Frm_Size { get { return new Size(Frm_Width, Frm_Height); } set { Frm_Width = value.Width; Frm_Height = value.Height; } }

        public int ClmWidth1_Name = 150;
        public int ClmWidth2_Name = 150;
        public int ClmWidth1_Comm = 150;
        public int ClmWidth2_Comm = 150;

        public Settings()
        {
            if (File.Exists(Gl.fPathSett))
            {
                String[] strl = File.ReadAllLines(Gl.fPathSett, Encoding.UTF8);
                for (int i = 0; i < strl.Length; i++)
                {
                    string[] sl = strl[i].Split(new Char[1] { '=' }, 2);
                    if (sl.Length < 2) continue;
                    string key = sl[0].Trim();
                    string val = sl[1].Trim();
                    if (key == "Login") Login = val;
                    if (key == "Pass") PassMd5 = val;
                    if (key == "DEBUG_MODE") DEBUG_MODE = val == "1";
                    if (key == "WRITE_LOG") WRITE_LOG = val == "1";
                    if (key == "Guests") Guests = val == "1";
                    if (key == "Sql_URL") Sql_URL = val;
                    if (key == "Sql_DBName") Sql_DBName = val;
                    if (key == "Sql_Login") Sql_Login = val;
                    if (key == "Sql_Pass")
                    {
                        string str = ESpace.Cmpr.DecompressText(Convert.FromBase64String(val));
                        if (str.Length >= 4)
                        {
                            Sql_Pass = ESpace.Cmpr.DecompressText(Convert.FromBase64String(val)).Substring(4);
                        }
                    }
                    if (key == "Frm_Top") { int tmp; if (int.TryParse(val, out tmp)) { Frm_Top = tmp; } }
                    if (key == "Frm_Left") { int tmp; if (int.TryParse(val, out tmp)) { Frm_Left = tmp; } }
                    if (key == "Frm_Width") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 669)tmp = 669; Frm_Width = tmp; } }
                    if (key == "Frm_Height") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 380)tmp = 380; Frm_Height = tmp; } }

                    if (key == "ClmWidth1_Name") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 50) tmp = 50; ClmWidth1_Name = tmp; } }
                    if (key == "ClmWidth2_Name") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 50) tmp = 50; ClmWidth2_Name = tmp; } }
                    if (key == "ClmWidth1_Comm") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 50) tmp = 50; ClmWidth1_Comm = tmp; } }
                    if (key == "ClmWidth2_Comm") { int tmp; if (int.TryParse(val, out tmp)) { if (tmp < 50) tmp = 50; ClmWidth2_Comm = tmp; } }

                    // загрузка настроек модулей
                    foreach (KeyValuePair<string, IElementUnit> u in Gl.unL)
                        for (int j = 0; j < u.Value.SettEl.Count; j++)
                            LoadEl(u.Value.SettEl[j], key, val);

                    // загрузка настроек внелементов
                    /*foreach (ExternalElement ext in Gl.exl)
                        for (int j = 0; j < ext.SettEl.Count; j++)
                            LoadEl(ext.SettEl[j], key, val);*/

                }
            }
        }

        void LoadEl(SettingsElement sElm, string key, string val)
        {
            switch (sElm.Tp)
            {
                case SettingsElType.String:
                    {
                        if (key == sElm.FieldName) sElm.Value = val;
                        break;
                    }
                case SettingsElType.Int:
                    {
                        if (key == sElm.FieldName)
                        {
                            int n = 0;
                            if (int.TryParse(val, out n)) sElm.Value = n;
                        }
                        break;
                    }
                case SettingsElType.Double:
                    {
                        if (key == sElm.FieldName)
                        {
                            double d = 0;
                            if (double.TryParse(val, out d)) sElm.Value = d;
                        }
                        break;
                    }
                case SettingsElType.Bool:
                    {
                        if (key == sElm.FieldName) sElm.Value = val == "1";
                        break;
                    }
            }
        }

        public void Save()
        {
            List<string> strL = new List<string>();
            strL.Add("Login = " + Login);
            strL.Add("Pass = " + PassMd5);
            strL.Add("Guests = " + (Guests ? "1" : "0"));

            strL.Add("DEBUG_MODE = " + (DEBUG_MODE ? "1" : "0"));
            strL.Add("WRITE_LOG = " + (WRITE_LOG ? "1" : "0"));

            strL.Add("Sql_URL = " + Sql_URL);
            strL.Add("Sql_DBName = " + Sql_DBName);
            strL.Add("Sql_Login = " + Sql_Login);
            strL.Add("Sql_Pass = " + Convert.ToBase64String(ESpace.Cmpr.CompressText("соль" + Sql_Pass)));

            strL.Add("Sql_Login = " + Sql_Login);

            strL.Add("Frm_Top = " + Frm_Top.ToString());
            strL.Add("Frm_Left = " + Frm_Left.ToString());
            strL.Add("Frm_Width = " + Frm_Width.ToString());
            strL.Add("Frm_Height = " + Frm_Height.ToString());

            strL.Add("ClmWidth1_Name = " + ClmWidth1_Name.ToString());
            strL.Add("ClmWidth2_Name = " + ClmWidth2_Name.ToString());
            strL.Add("ClmWidth1_Comm = " + ClmWidth1_Comm.ToString());
            strL.Add("ClmWidth2_Comm = " + ClmWidth2_Comm.ToString());

            // сохранение настроек модулей
            foreach (KeyValuePair<string, IElementUnit> u in Gl.unL)
            {
                for (int j = 0; j < u.Value.SettEl.Count; j++)
                {
                    SettingsElement sElm = u.Value.SettEl[j];
                    if (sElm.Tp != SettingsElType.Bool) strL.Add(sElm.FieldName + " = " + sElm.Value.ToString());
                    else strL.Add(sElm.FieldName + " = " + (((bool)sElm.Value) ? "1" : "0"));
                }
            }

            // сохранение настроек внелементов
            /*foreach (ExternalElement ext in Gl.exl)
            {
                for (int j = 0; j < ext.SettEl.Count; j++)
                {
                    SettingsElement sElm = ext.SettEl[j];
                    if (sElm.Tp != SettingsElType.Bool) strL.Add(sElm.FieldName + " = " + sElm.Value.ToString());
                    else strL.Add(sElm.FieldName + " = " + (((bool)sElm.Value) ? "1" : "0"));
                }
            }*/

            File.WriteAllLines(Gl.fPathSett, strL.ToArray(), Encoding.UTF8);
        }

    }

    /// <summary> класс для списка имен удаленных элементов </summary>
    public class DeleteNetCollection
    {
        List<string> strL = new List<string>();
        string FPath;

        public int Count { get { return strL.Count; } }

        public string this[int i] { get { return strL[i]; } }

        public DeleteNetCollection(string FPath)
        {
            this.FPath = FPath;
            Load();
        }

        public void Add(string str) { strL.Add(str); Save(); }

        public void Clear() { strL.Clear(); Save(); }

        void Load()
        {
            if (File.Exists(FPath)) strL = new List<string>(File.ReadAllLines(FPath, Encoding.UTF8));
        }

        void Save()
        {
            File.WriteAllLines(FPath, strL.ToArray(), Encoding.UTF8);
        }
    }

    #region *** АБСТРАКТНЫЕ КЛАССЫ ЭЛЕМЕНТОВ *******************************************

    public enum NetStatus { Local, Actual, NotActual, Write }

    public delegate void ElementFunction(Element elm);
    public delegate void EmptyFunction();

    /// <summary> Апи для подключаемых модулей </summary>
    public class API_Funtions
    {
        /// <summary> Удаление элемента. (Удаляет элемент; добавляет элемент в список на удаление для сервера, если элемент сетевой) </summary>
        public ElementFunction DeleteElement = null;
        /// <summary> Обновляет отображаемый каталог лист, если его сожержимое отображается в основном окне. (В параметре указывается обновляемый каталог) </summary>
        public ElementFunction RefillDirIfShow = null;
        /// <summary> Добавляет имя в список на удаление для сервера, если элемент сетевой </summary>
        public ElementFunction AddElementInDelList = null;
        /// <summary> Обновляет иконки элементов в указанногм каталоге, если его сожержимое отображается в основном окне </summary>
        public ElementFunction UpdateIcoInDirIfShow = null;
        /// <summary> Сохранние базы на локальный диск </summary>
        public EmptyFunction SaveData;
        /// <summary> Сохранние настроек </summary>
        public EmptyFunction SaveSettings;
        /// <summary> Обновление комментариев </summary>
        public ElementFunction UpdateComments;

        public API_Funtions(ElementFunction DeleteElement, ElementFunction RefillDirIfShow, ElementFunction AddElementInDelList, ElementFunction UpdateIcoInDirIfShow, EmptyFunction SaveData, EmptyFunction SaveSettings, ElementFunction UpdateComments)
        {
            this.DeleteElement = DeleteElement;
            this.RefillDirIfShow = RefillDirIfShow;
            this.AddElementInDelList = AddElementInDelList;
            this.UpdateIcoInDirIfShow = UpdateIcoInDirIfShow;
            this.SaveData = SaveData;
            this.SaveSettings = SaveSettings;
            this.UpdateComments = UpdateComments;
        }

    }

    public abstract class IElementUnit
    {
        public abstract string Ext { get; }

        protected bool isVisibleUnit = true;
        public bool IsVisibleUnit { get { return isVisibleUnit; } }

        public API_Funtions API_Func;

        public IElementUnit(API_Funtions API_Func)
        {
            this.API_Func = API_Func;
        }

        public List<SettingsElement> SettEl = new List<SettingsElement>();

        #region *** события ***************************************

        /// <summary> Функция для загрузки из файла дочернего элементов </summary>
        public abstract Element LoadElement(EStream st, DBItem Parent);

        /// <summary> Функция для создания элемента по данным с БД сервера </summary>
        public abstract Element CreateElementBySourceDate(string Name, DBItem Parent, string SourceData);

        /// <summary> событие возникает после загрузки базы. Сам юнит создается до загрузки базы
        /// событие используется для выполнения начальных дейтсвий с уже загруженой базой </summary>
        public abstract void EndCreate();

        /// <summary> Событие, вызывающееся каждый раз по срабатыванию таймера </summary>
        /// <param name="tmrDt">Единая дата таймера для этого цикла</param>
        public abstract void TimerTick(DateTime tmrDt, DIR DB);

        /// <summary> событие изменения режима гости </summary>
        public abstract void GuestModeChange(bool bGuest);

        #endregion *** события ***************************************

    }

    public abstract class VisibleUnit : IElementUnit
    {
        public abstract string MenuName { get; }

        public List<int> icoIndL = new List<int>();
        public readonly List<Bitmap> IcoList;
        protected abstract List<Bitmap> GetIcoList();

        /// <summary> Конструктор </summary>
        /// <param name="DeleteElement">Удаление элемента. Удаляет элемент, переносит его в корзину, если указано, обновляет отображаемый лист, если необходимо</param>
        /// <param name="RefillDir">Обновляет отображаемый лист, если необходимо. В параметре указывается измененный каталог</param>
        public VisibleUnit(API_Funtions API_Func)
            : base(API_Func)
        {
            IcoList = GetIcoList();
            isVisibleUnit = true;
        }

        public abstract Element CreateNew(DIR Parent, NetStatus NetStat);

        public abstract bool Edit(Element EditEl);

        public abstract int GetNumIco(Element el);

    }

    // модуль для скрытых элементов
    // элементы так же хранятся в общей базе
    // для каждого элемента создается 2 каталога
    // первый каталог создается в локальном системном каталоге
    // второй каталог создается в сетевом системном каталоге
    // имена каталогов создаются как LocDir~расширение и NetDir~расширение
    // каталоги синхронизируются как и обычные, но в дерево каталогов не выводятся, если не включен отладочный режим с показом скрытых каталогов
    // элементы в каталогах добавляются/редактируются/удаляются внутренними средствами класса HiderUnit
    // для управления извне, можно использовать кнопки, встраиваемые в меню трея и в меню тулбокса основного окна
    // при удалении сетевого элемента необходимо вызывать функцию AddElementInDelList, которая добавляет имя элемента в список для удаления
    public abstract class HiderUnit : IElementUnit
    {
        #region *** кнопки ********************************

        // отображение кнопок в меню трея и тулбоксе:
        // если кнопок, нет, не отображается
        // если кнопка одна, отображается как кнопка
        // если кнопок несколько, отображается кнопка-меню, в котором содержатся остальные кнопки

        /// <summary> кнопка в трее </summary>
        public ToolStripItem NotifyMenuBotton = null;

        /// <summary> список кнопок меню окна</summary>
        //public readonly List<ToolStripButton> ToolMenuBottons;

        #endregion *** кнопки ********************************

        public HiderUnit(API_Funtions API_Func)
            : base(API_Func)
        {
            isVisibleUnit = false;
        }

        /// <summary> событие, возникающее после синхронизации </summary>
        public abstract void EndSinhronize(Gl.SinhronizeResult result);

        public DIR GetLoacalDir() { DIR LocDir = Gl.GetSystemDir(Gl.SystemDir.Local); return (DIR)LocDir.Items[LocDir.IndexOfRecord(Gl.LocHideDirName + Ext, "")]; }

        public DIR GetNetDir() { DIR netDir = Gl.GetSystemDir(Gl.SystemDir.Net); return (DIR)netDir.Items[netDir.IndexOfRecord(Gl.NetHideDirName + Ext, "")]; }

        public List<Element> GetAllElements()
        {
            DIR LocDir = GetLoacalDir();
            DIR NetDir = GetNetDir();

            List<Element> ret = new List<Element>();
            for (int i = 0; i < LocDir.Items.Count; i++) ret.Add((Element)LocDir.Items[i]);
            for (int i = 0; i < NetDir.Items.Count; i++) ret.Add((Element)NetDir.Items[i]);

            return ret;
        }

    }

    public abstract class Element : DBItem, IDisposable
    {
        public long ServVertion = 0;
        int ActVer = 1;
        public abstract String SourceData { get; set; }

        public NetStatus NetStat = NetStatus.Local;

        /// <summary> в данный момент элемент редактируется (Синхронизация будет ожидать завершения редактирования всех элементов)</summary>
        public bool IsEdit = false;

        // для синхронизации
        public bool bSinh = false;

        /// <summary> Скрытый элемент </summary>
        public bool IsHider { get { if (!Glob.IsVisibleUnit) return true; return Ext == "" && ((DIR)this).IsHide; } }

        /// <summary> Элемент никогда не записывался на сервер. То есть, новый добавленый </summary>
        public bool IsNew
        {
            get { return ServVertion == 0; }
            set
            {
                if (value == true)
                {
                    ServVertion = 0;
                    if (NetStat != NetStatus.Local) NetStat = NetStatus.NotActual;
                }
                else
                {
                    throw new Exception("невозможно присвоить элементу статус отредактирован");
                }
            }
        }

        /// <summary>Создание нового элемента программно</summary>
        public Element(string Name, bool IsDirectory, DBItem Parent, NetStatus NetStat)
            : base(Name, IsDirectory, Parent)
        {
            this.NetStat = NetStat;
        }

        public Element(EStream st, DBItem Parent)
            : base(st, Parent)
        {
            int LoadVer = st.LoadInt();
            switch (LoadVer)
            {
                case 0: Load0(st); break;
                case 1: Load1(st); break;
                default: throw new Exception("Невозможно загрузить элемент, так как версия не поддерживается");
            }
        }

        void Load0(EStream st)
        {
            ServVertion = 0; st.LoadDate();
            NetStat = (NetStatus)st.LoadInt();
        }

        void Load1(EStream st)
        {
            ServVertion = st.LoadLong();
            NetStat = (NetStatus)st.LoadInt();
        }

        public override void Save(EStream st)
        {
            base.Save(st);
            st.SaveInt(ActVer);

            st.SaveLong(ServVertion);
            st.SaveInt((int)NetStat);
        }

        /// <summary> Попытка синхронизации неактуального элемента. 
        /// Попытка синхронизировать некритичные данные с новыми данными этого элемента
        /// Возвращает, удалось ли синхронизировать. 
        /// </summary>
        public virtual TrySynchronizeResult TrySynchronize(Element el) { return TrySynchronizeResult.SourceDataCanBeReplaced; }

        /// <summary> Событие, вызывающееся каждый раз по срабатыванию таймера </summary>
        /// <param name="tmrDt">Единая дата таймера для этого цикла</param>
        public abstract void TimerTick(DateTime tmrDt, DIR DB);

        public IElementUnit Glob { get { return (IElementUnit)Gl.unL[Ext]; } }

        public abstract void Dispose();

        /// <summary> Получить комментарий к видимому элементу </summary>
        public virtual string GetComment()
        {
            return " ";
        }

    }

    #endregion *** АБСТРАКТНЫЕ КЛАССЫ ЭЛЕМЕНТОВ *******************************************

    public enum eeNetStatus { Actual, NotActual, Write }

    public class ExtElementUnit
    {
        string fPath;

        /// <summary> версия на сервере </summary>
        public long ServVertion = 0;

        public eeNetStatus NetStat = eeNetStatus.NotActual;

        /// <summary> внелемент </summary>
        public ExternalElement ExElm;

        /// <summary>Ожидающие синхронизации новые данные с сервера</summary>
        public byte[] SyncSourceData = null;
        /// <summary>Ожидающая синхронизации новая версия</summary>
        public long SyncServVer = 0;

        /// <summary>Время следующей плановой проверки серверной версии</summary>
        public DateTime dtNextGetVertion = DateTime.MinValue;

        public ExtElementUnit(ExternalElement ExElm)
        {
            this.ExElm = ExElm;
            fPath = Gl.dPathExternalNetLocalData + "\\" + ExElm.Name + "_" + Gl.sett.Login + ".db";
            LoadData();
        }

        void LoadData()
        {
            if (!File.Exists(fPath)) return;
            EStream st = new EStream(fPath);
            st.OpenLoad();

            st.LoadInt(); // задел на версионность

            ServVertion = st.LoadLong();
            ExElm.ChangeSourceData(ChangeDataEvents.LoadAfterInit, st.LoadBuf());
            NetStat = (eeNetStatus)st.LoadInt();

            st.Close();

            #region *** история *********
            string dPath = Gl.dPathExternalNetHistory + "\\" + ExElm.Name;
            if (!Directory.Exists(dPath)) Directory.CreateDirectory(dPath);
            SaveData(dPath + "\\LocalData_" + Comm.GetValidFileName(Gl.sett.Login) + " [" + DateTime.Now.ToString("dd.MM.yyyy HH-mm") + "].db");
            // удаление старой истории
            string[] fl = Directory.GetFiles(dPath);
            for (int i = 0; i < fl.Length; i++)
            {
                string s = fl[i];
                int ind1 = s.IndexOf('[');
                int ind2 = s.IndexOf(']');
                if (ind1 == -1 || ind2 == -1) continue;
                DateTime dt;
                if (!DateTime.TryParse(s.Substring(ind1 + 1, 16).Replace("-", ":") + ":00", out dt)) continue;
                if (dt < DateTime.Now.AddDays(-30)) File.Delete(fl[i]);
            }
            #endregion *** история *********
        }

        public void SaveData()
        {
            SaveData(fPath);
        }

        void SaveData(string fPath)
        {
            EStream st = new EStream(fPath);
            st.OpenSave();

            st.SaveInt(0); // задел на версионность

            st.SaveLong(ServVertion);
            st.SaveBuf(ExElm.GetSourceData());
            st.SaveInt((int)NetStat);

            st.Close();
        }

    }

    #region *** КЛАССЫ ВНЕШНИХ EXE ********************************************************

    /// <summary> Флаги событий открытия/закрытия
    /// 0 - ничего
    /// 1 - по выбору из меню
    /// 2 - при старте органайзера
    /// 4 - при установке режима "гости" или при старте органайзера в режиме "гости"
    /// 8 - при снятии режима "гости" или при старте органайзера НЕ в режиме "гости"
    /// </summary>
    public enum ExExeStartEventFlags
    {
        None = 0,
        ClickMenu = 1,
        StartOrganizer = 2,
        GuestMode = 4,
        NotGuestMode = 8
    }

    public enum ExExeStopEventFlags { None, SetGuestMode, ResetGuestMode }

    /// <summary>Внешнее приложение</summary>
    public class ExternalExe
    {
        public string FLocalPathExe;
        System.Diagnostics.Process proc = null;
        public string MenuNameStart = "";

        public ToolStripMenuItem MenuItm = null;
        ContextMenuStrip menu;

        public ExExeStartEventFlags StartEvents = ExExeStartEventFlags.None;
        public ExExeStopEventFlags StopEvents = ExExeStopEventFlags.None; // Можно все, кроме меню

        /*public ExternalExe(string FLocalPath, string MenuNameStart, ExternalExeEvents StartEvent, string MenuNameClose, ExternalExeEvents CloseEvent)
        {
            this.FLocalPathExe = FLocalPath;
            this.MenuNameStart = MenuNameStart.Trim();
            this.StartEvent = StartEvent;
            this.MenuNameClose = MenuNameClose;
            this.CloseEvent = CloseEvent;
            CreateMenuItem();
        }*/

        public ExternalExe(string fPath, ContextMenuStrip menu)
        {
            string[] strL = File.ReadAllLines(fPath, Encoding.UTF8);
            foreach (string str in strL)
            {
                if (str.Trim() == "") continue;
                string[] sl = str.Split(new char[1] { '=' }, 2);
                string key = sl[0].Trim();
                string val = sl[1].Trim();
                switch (key)
                {
                    case "FLocalPath": FLocalPathExe = val; break;
                    case "MenuNameStart": MenuNameStart = val; break;
                    case "StartEvents": StartEvents = GetExternalExeEventsFromString(val); break;
                    case "StopEvents": StopEvents = (ExExeStopEventFlags)Enum.Parse(typeof(ExExeStopEventFlags), val); break;
                }
            }
            this.menu = menu;
            CreateMenuItem();
        }

        public void Save(string InfoName)
        {
            List<string> sl = new List<string>();
            sl.Add("FLocalPath = " + FLocalPathExe);
            sl.Add("MenuNameStart = " + MenuNameStart);
            sl.Add("StartEvents = " + GetExternalExeEventsFlagsString(StartEvents));
            sl.Add("StopEvents = " + StopEvents.ToString());
        }

        ExExeStartEventFlags GetExternalExeEventsFromString(string s)
        {
            ExExeStartEventFlags ret = ExExeStartEventFlags.None;
            string[] sl = s.Split('|');
            foreach (string str in sl) if (str.Trim() != "") ret = ret | (ExExeStartEventFlags)Enum.Parse(typeof(ExExeStartEventFlags), str.Trim());
            return ret;
        }

        string GetExternalExeEventsFlagsString(ExExeStartEventFlags val)
        {
            string ret = "";
            ExExeStartEventFlags[] allVals = (ExExeStartEventFlags[])Enum.GetValues(typeof(ExExeStartEventFlags));
            foreach (ExExeStartEventFlags v in allVals) if ((val & v) != ExExeStartEventFlags.None) ret += " | " + (val & v).ToString();
            return ret.Substring(3);
        }

        void CreateMenuItem()
        {
            if ((StartEvents & ExExeStartEventFlags.ClickMenu) != ExExeStartEventFlags.None)
            {
                MenuItm = new ToolStripMenuItem(MenuNameStart);
                MenuItm.Click += new EventHandler(MenuItem_Click);
            }
        }

        // запуск вручную по кнопке меню
        void MenuItem_Click(object Sender, EventArgs e)
        {
            if (proc == null)
            {
                if (!Start()) MessageBox.Show("Не удалось запустить модуль. Возможно исполняемый файл отсутствует или заблокирован.");
            }
            else
            {
                MessageBox.Show("Модуль уже запущен");
            }
        }

        void Proc_Exited(object Sender, EventArgs e)
        {
            proc.Dispose();
            proc = null;
            //if (MenuItm != null) menu.Invoke(new Action(() => { MenuItm.Enabled = true; }));
            //if (MenuItm != null) menu.Invoke(new MethodInvoker(delegate() { MenuItm.Enabled = true; }));
        }

        public bool Start()
        {
            // если, процесс уже запущен, ничего не делаем
            if (proc != null) return true;

            string fPath = Gl.dPathExternalExe + "\\" + FLocalPathExe;
            if (!File.Exists(fPath)) return false;
            try
            {
                proc = new System.Diagnostics.Process();
                //proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                //proc.StartInfo.Arguments = "-j \"" + fPathOutput + "\" \"" + file.FullName + "\"";
                proc.StartInfo.FileName = Gl.dPathExternalExe + "\\" + FLocalPathExe;
                proc.EnableRaisingEvents = true; // активировать срабатывание события по завершению процесса
                proc.Exited += new EventHandler(Proc_Exited);
                proc.Start();
                //if (MenuItm != null) MenuItm.Enabled = false;

                return true;
            }
            catch { proc = null; return false; }
        }

        public void Stop()
        {
            if (proc != null && !proc.HasExited) proc.CloseMainWindow();
        }

        public void GuestModeChange(bool bGuest)
        {
            if ((StartEvents & ExExeStartEventFlags.GuestMode) != ExExeStartEventFlags.None && bGuest) Start();
            if (StopEvents == ExExeStopEventFlags.SetGuestMode && bGuest) Stop();

            if ((StartEvents & ExExeStartEventFlags.NotGuestMode) != ExExeStartEventFlags.None && !bGuest) Start();
            if (StopEvents == ExExeStopEventFlags.ResetGuestMode && !bGuest) Stop();
        }

    }

    #endregion *** КЛАССЫ ВНЕШНИХ EXE ********************************************************

}
